package com.support.mvc.entity.generator;

import com.utils.util.Util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;

import java.io.Serializable;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;

import static com.utils.util.Dates.Pattern.yyyyMMddHHmmssSSS;

/**
 * <pre>
 * 生成 17 位 有序 id，同一毫秒生成的 id 不是绝对有序的
 * 规则： 17位日期(yyyyMMddHHmmssSSS) + 10位随机数字(RandomStringUtils.randomNumeric(10)) + 5位mac地址之和(和不足5位前面补0，超过5位从右侧开始截取5位)
 * 按规则拼接字符串之后包装成 BigInteger 进行 Base62 转换缩短长度，获得 18 位有序的 id
 *
 * 使用参考：在实体类主键 ID 字段使用注解
 * \@GeneratedValue(generator = "base18")
 * \@GenericGenerator(name = "base18", strategy = "com.support.mvc.entity.generator.Base18Generator")
 *  private String id;
 *
 * @author 谢长春 2019/9/17
 */
@Slf4j
public class Base18Generator implements IdentifierGenerator {

    private static final String BASE_62_CHAR = "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
    private static final int BASE_62_CHAR_LENGTH = BASE_62_CHAR.length();
    /**
     * 随机数长度
     */
    private static final int RANDOM_LENGTH = 10;
    /**
     * 所有 mac 地址的二进制之和，不足5位前面补0，超过5位从右侧开始截取5位
     */
    private static String MAC_SUM = "00000";

    static {
        try {
            int macSum = 0;
            for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
                final NetworkInterface networkInterface = networkInterfaces.nextElement();
                if (networkInterface.isLoopback()
                        || networkInterface.isVirtual()
                        || !networkInterface.isUp()
                        || !isIpV4(networkInterface.getInetAddresses())
                ) {
                    continue;
                }
                final byte[] mac = networkInterface.getHardwareAddress();
                if (mac != null) {
                    final StringBuilder sb = new StringBuilder();
                    for (byte b : mac) {
                        macSum += Byte.toUnsignedLong(b);
                        sb.append(String.format("%02X", b));
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("mac:{} => {} - {}", sb, networkInterface.getName(), networkInterface.getDisplayName());
                    }
                }
            }
            MAC_SUM = StringUtils.right(String.format("%05d", macSum), MAC_SUM.length());
            if (log.isDebugEnabled()) {
                log.debug("MAC_SUM:{}", MAC_SUM);
            }
        } catch (Exception e) {
            log.error("MAC地址获取失败", e);
        }
    }

    /**
     * 判断是否为 ipv4 地址
     *
     * @param address Enumeration<InetAddress>
     * @return true：是，false：否
     */
    public static boolean isIpV4(final Enumeration<InetAddress> address) {
        while (address.hasMoreElements()) {
            InetAddress inetAddress = address.nextElement();
            if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 从十进制 转 62 进制
     *
     * @param value {@link Long} 十进制数字
     * @return {@link String} 62进制字符串
     */
    private static String encodeBase62(BigInteger value) {
        if (BigInteger.valueOf(BASE_62_CHAR_LENGTH).compareTo(value) > 0) {
            return BASE_62_CHAR.charAt(value.intValue()) + "";
        }
        final BigInteger baseLength = BigInteger.valueOf(BASE_62_CHAR_LENGTH);
        final StringBuilder sb = new StringBuilder();
        int rem;
        while (!Objects.equals(BigInteger.ZERO, value)) {
            rem = value.remainder(baseLength).intValue();
            sb.append(BASE_62_CHAR.charAt(rem));
            value = value.divide(baseLength);
        }
        return sb.reverse().toString();
    }

    @Override
    public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {

    }

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        return encodeBase62(new BigInteger(yyyyMMddHHmmssSSS.now()
                .concat(RandomStringUtils.randomNumeric(RANDOM_LENGTH))
                .concat(MAC_SUM)
        ));
    }

    @SneakyThrows
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            System.out.println(i + ":" + Util.encodeBase62BigInteger(new BigInteger(yyyyMMddHHmmssSSS.now() + RandomStringUtils.randomNumeric(10) + MAC_SUM)));
        }
        // 测试 10w id生成时间
        System.out.printf("10w 耗时 %d ms%n", System.currentTimeMillis() - start);

        {// 测试最小长度
            BigInteger val = new BigInteger(yyyyMMddHHmmssSSS.now() + "0000000000" + "00000");
            String encode = Util.encodeBase62BigInteger(val);
            BigInteger decode = Util.decodeBase62BigInteger(encode);
            System.out.printf("val:%d%nencode:%s => %d%ndecode:%d => %d%n", val, encode, encode.length(), decode, decode.toString().length());
        }
        {// 测试最大长度
            BigInteger val = new BigInteger("99991231235959999" + "9999999999" + "99999");
            String encode = Util.encodeBase62BigInteger(val);
            BigInteger decode = Util.decodeBase62BigInteger(encode);
            System.out.printf("val:%d%nencode:%s => %d%ndecode:%d => %d%n", val, encode, encode.length(), decode, decode.toString().length());
        }
        {
            Base18Generator generator = new Base18Generator();
            String encode = (String) generator.generate(null, null);
            BigInteger decode = Util.decodeBase62BigInteger(encode);
            System.out.printf("encode:%s => %d%ndecode:%d => %d%n", encode, encode.length(), decode, decode.toString().length());
        }

        { // 检查重复概率
            Set<String> ids = new HashSet<>();
            Base18Generator generator = new Base18Generator();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        String id = (String) generator.generate(null, null);
                        if (ids.contains(id)) {
                            System.err.println(id + "重复:" + Util.decodeBase62BigInteger(id));
                        } else {
                            ids.add(id);
                        }
                    }
                }).start();
            }
        }
    }
}
